﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace Yyyg.WebUI
{
    public static class RSACryptoHelper
    {
        /// <summary>
        /// 取得私钥和公钥 XML 格式,返回数组第一个是私钥,第二个是公钥.
        /// </summary>
        /// <param name="size">密钥长度,默认1024,可以为2048</param>
        /// <returns></returns>
        public static Tuple<string, string> CreateXmlKey(int size = 1024)
        {
            //密钥格式要生成pkcs#1格式的  而不是pkcs#8格式的
            RSACryptoServiceProvider sp = new RSACryptoServiceProvider(size);
            string privateKey = sp.ToXmlString(true);//private key
            string publicKey = sp.ToXmlString(false);//public  key
            return new Tuple<string, string>(privateKey, publicKey);
        }

        /// <summary>
        /// 取得私钥和公钥 CspBlob 格式,返回数组第一个是私钥,第二个是公钥.
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        public static Tuple<string, string> CreateCspBlobKey(int size = 1024)
        {
            //密钥格式要生成pkcs#1格式的  而不是pkcs#8格式的
            RSACryptoServiceProvider sp = new RSACryptoServiceProvider(size);
            string privateKey = Convert.ToBase64String(sp.ExportCspBlob(true));//private key
            string publicKey = Convert.ToBase64String(sp.ExportCspBlob(false));//public  key 

            return new Tuple<string, string>(privateKey, publicKey);
        }

        /// <summary>
        /// 导出PEM PKCS#1格式密钥对，返回数组第一个是私钥,第二个是公钥.
        /// </summary>
        public static Tuple<string, string> CreateKey_PEM_PKCS1(int size = 1024)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
            string privateKey = RSA_PEM.ToPEM(rsa, false, false);
            string publicKey = RSA_PEM.ToPEM(rsa, true, false);
            return new Tuple<string, string>(privateKey, publicKey);
        }

        /// <summary>
        /// 导出PEM PKCS#8格式密钥对，返回数组第一个是私钥,第二个是公钥.
        /// </summary>
        public static Tuple<string, string> CreateKey_PEM_PKCS8(int size = 1024, bool convertToPublic = false)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(size);
            string privateKey = RSA_PEM.ToPEM(rsa, false, true);
            string publicKey = RSA_PEM.ToPEM(rsa, true, true);
            return new Tuple<string, string>(privateKey, publicKey);
        }

        /// <summary>
        /// 加密 用的是PEM格式的密钥
        /// </summary>
        /// <param name="str_Plain_Text">要加密的数据</param>
        /// <param name="str_Public_PEMKey">Xml格式的公钥</param>
        /// <returns></returns>
        public static string Encrypt_PEMKey(string str_Plain_Text, string str_Public_PEMKey)
        {
            using (RSACryptoServiceProvider RSA = RSA_PEM.FromPEM(str_Public_PEMKey))
            {
                return Encrypt(str_Plain_Text, RSA);
            }
        }

        /// <summary>
        /// 加密 用的是Xml格式的密钥
        /// </summary>
        /// <param name="str_Plain_Text">要加密的数据</param>
        /// <param name="str_Public_XmlKey">Xml格式的公钥</param>
        /// <returns></returns>
        public static string Encrypt_XmlKey(string str_Plain_Text, string str_Public_XmlKey)
        {
            using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
            {

                RSA.FromXmlString(str_Public_XmlKey);//载入公钥

                return Encrypt(str_Plain_Text, RSA);
            }
        }

        private static string Encrypt(string str_Plain_Text, RSACryptoServiceProvider RSA)
        {
            var data = Encoding.UTF8.GetBytes(str_Plain_Text);
            int buffersize = (RSA.KeySize / 8) - 11;
            var buffer = new byte[buffersize];
            using (MemoryStream input = new MemoryStream(data), output = new MemoryStream())
            {
                while (true)
                {
                    int readsize = input.Read(buffer, 0, buffersize);
                    if (readsize <= 0)
                    {
                        break;
                    }
                    var temp = new byte[readsize];
                    Array.Copy(buffer, 0, temp, 0, readsize);
                    var EncBytes = RSA.Encrypt(temp, false);
                    output.Write(EncBytes, 0, EncBytes.Length);
                }
                return Convert.ToBase64String(output.ToArray());
            }
        }

        /// <summary>
        /// 解密 用的是Xml格式的密钥
        /// </summary>
        /// <param name="str_Cypher_Text">密文</param>
        /// <param name="str_Private_Key">密钥</param>
        /// <returns></returns>
        public static string Decrypt_XmlKey(string str_Cypher_Text, string str_Private_XmlKey)
        {
            using (var RSA = new RSACryptoServiceProvider())
            {
                RSA.FromXmlString(str_Private_XmlKey);
                return Decrypt(str_Cypher_Text, RSA);
            }
        }


        /// <summary>
        /// 解密 用的是PEM格式的密钥
        /// </summary>
        /// <param name="str_Cypher_Text">密文</param>
        /// <param name="str_Private_Key">密钥</param>
        /// <returns></returns>
        public static string Decrypt_PEMKey(string str_Cypher_Text, string str_Private_PEMKey)
        {
            //using (var RSA = new RSACryptoServiceProvider())
            using (var RSA = RSA_PEM.FromPEM(str_Private_PEMKey))
            {
                return Decrypt(str_Cypher_Text, RSA);
            }
        }

        private static string Decrypt(string str_Cypher_Text, RSACryptoServiceProvider RSA)
        {
            var data = Convert.FromBase64String(str_Cypher_Text);

            //RSA.FromXmlString(str_Private_Key);
            int buffersize = RSA.KeySize / 8;
            var buffer = new byte[buffersize];
            using (MemoryStream input = new MemoryStream(data), output = new MemoryStream())
            {
                while (true)
                {
                    int readsize = input.Read(buffer, 0, buffersize);
                    if (readsize <= 0)
                    {
                        break;
                    }

                    var temp = new byte[readsize];
                    Array.Copy(buffer, 0, temp, 0, readsize);
                    var DecBytes = RSA.Decrypt(temp, false);
                    output.Write(DecBytes, 0, DecBytes.Length);
                }
                return Encoding.UTF8.GetString(output.ToArray());
            }
        }
    }

    /// <summary>
    /// RSA PEM格式秘钥对的解析和导出
    /// </summary>
    class RSA_PEM
    {
        /// <summary>
        /// 用PEM格式密钥对创建RSA，支持PKCS#1、PKCS#8格式的PEM
        /// </summary>
        public static RSACryptoServiceProvider FromPEM(string pem)
        {
            var rsaParams = new CspParameters();
            rsaParams.Flags = CspProviderFlags.UseMachineKeyStore;
            var rsa = new RSACryptoServiceProvider(rsaParams);

            var param = new RSAParameters();

            var base64 = _PEMCode.Replace(pem, "");
            var data = RSA_Unit.Base64DecodeBytes(base64);
            if (data == null)
            {
                throw new Exception("PEM内容无效");
            }
            var idx = 0;

            //读取长度
            Func<byte, int> readLen = (first) =>
            {
                if (data[idx] == first)
                {
                    idx++;
                    if (data[idx] == 0x81)
                    {
                        idx++;
                        return data[idx++];
                    }
                    else if (data[idx] == 0x82)
                    {
                        idx++;
                        return (((int)data[idx++]) << 8) + data[idx++];
                    }
                    else if (data[idx] < 0x80)
                    {
                        return data[idx++];
                    }
                }
                throw new Exception("PEM未能提取到数据");
            };
            //读取块数据
            Func<byte[]> readBlock = () =>
            {
                var len = readLen(0x02);
                if (data[idx] == 0x00)
                {
                    idx++;
                    len--;
                }
                var val = data.Sub(idx, len);
                idx += len;
                return val;
            };
            //比较data从idx位置开始是否是byts内容
            Func<byte[], bool> eq = (byts) =>
            {
                for (var i = 0; i < byts.Length; i++, idx++)
                {
                    if (idx >= data.Length)
                    {
                        return false;
                    }
                    if (byts[i] != data[idx])
                    {
                        return false;
                    }
                }
                return true;
            };




            if (pem.Contains("PUBLIC KEY"))
            {
                /****使用公钥****/
                //读取数据总长度
                readLen(0x30);
                if (!eq(_SeqOID))
                {
                    throw new Exception("PEM未知格式");
                }
                //读取1长度
                readLen(0x03);
                idx++;//跳过0x00
                      //读取2长度
                readLen(0x30);

                //Modulus
                param.Modulus = readBlock();

                //Exponent
                param.Exponent = readBlock();
            }
            else if (pem.Contains("PRIVATE KEY"))
            {
                /****使用私钥****/
                //读取数据总长度
                readLen(0x30);

                //读取版本号
                if (!eq(_Ver))
                {
                    throw new Exception("PEM未知版本");
                }

                //检测PKCS8
                var idx2 = idx;
                if (eq(_SeqOID))
                {
                    //读取1长度
                    readLen(0x04);
                    //读取2长度
                    readLen(0x30);

                    //读取版本号
                    if (!eq(_Ver))
                    {
                        throw new Exception("PEM版本无效");
                    }
                }
                else
                {
                    idx = idx2;
                }

                //读取数据
                param.Modulus = readBlock();
                param.Exponent = readBlock();
                param.D = readBlock();
                param.P = readBlock();
                param.Q = readBlock();
                param.DP = readBlock();
                param.DQ = readBlock();
                param.InverseQ = readBlock();
            }
            else
            {
                throw new Exception("pem需要BEGIN END标头");
            }

            rsa.ImportParameters(param);
            return rsa;
        }
        static private Regex _PEMCode = new Regex(@"--+.+?--+|\s+");
        static private byte[] _SeqOID = new byte[] { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
        static private byte[] _Ver = new byte[] { 0x02, 0x01, 0x00 };


        /// <summary>
        /// 将RSA中的密钥对转换成PEM格式，usePKCS8=false时返回PKCS#1格式，否则返回PKCS#8格式，如果convertToPublic含私钥的RSA将只返回公钥，仅含公钥的RSA不受影响
        /// </summary>
        public static string ToPEM(RSACryptoServiceProvider rsa, bool convertToPublic, bool usePKCS8)
        {
            //https://www.jianshu.com/p/25803dd9527d
            //https://www.cnblogs.com/ylz8401/p/8443819.html
            //https://blog.csdn.net/jiayanhui2877/article/details/47187077
            //https://blog.csdn.net/xuanshao_/article/details/51679824
            //https://blog.csdn.net/xuanshao_/article/details/51672547

            var ms = new MemoryStream();
            //写入一个长度字节码
            Action<int> writeLenByte = (len) =>
            {
                if (len < 0x80)
                {
                    ms.WriteByte((byte)len);
                }
                else if (len <= 0xff)
                {
                    ms.WriteByte(0x81);
                    ms.WriteByte((byte)len);
                }
                else
                {
                    ms.WriteByte(0x82);
                    ms.WriteByte((byte)(len >> 8 & 0xff));
                    ms.WriteByte((byte)(len & 0xff));
                }
            };
            //写入一块数据
            Action<byte[]> writeBlock = (byts) =>
            {
                var addZero = (byts[0] >> 4) >= 0x8;
                ms.WriteByte(0x02);
                var len = byts.Length + (addZero ? 1 : 0);
                writeLenByte(len);

                if (addZero)
                {
                    ms.WriteByte(0x00);
                }
                ms.Write(byts, 0, byts.Length);
            };
            //根据后续内容长度写入长度数据
            Func<int, byte[], byte[]> writeLen = (index, byts) =>
            {
                var len = byts.Length - index;

                ms.SetLength(0);
                ms.Write(byts, 0, index);
                writeLenByte(len);
                ms.Write(byts, index, len);

                return ms.ToArray();
            };


            if (rsa.PublicOnly || convertToPublic)
            {
                /****生成公钥****/
                var param = rsa.ExportParameters(false);


                //写入总字节数，不含本段长度，额外需要24字节的头，后续计算好填入
                ms.WriteByte(0x30);
                var index1 = (int)ms.Length;

                //固定内容
                // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
                ms.WriteAll(_SeqOID);

                //从0x00开始的后续长度
                ms.WriteByte(0x03);
                var index2 = (int)ms.Length;
                ms.WriteByte(0x00);

                //后续内容长度
                ms.WriteByte(0x30);
                var index3 = (int)ms.Length;

                //写入Modulus
                writeBlock(param.Modulus);

                //写入Exponent
                writeBlock(param.Exponent);


                //计算空缺的长度
                var byts = ms.ToArray();

                byts = writeLen(index3, byts);
                byts = writeLen(index2, byts);
                byts = writeLen(index1, byts);


                return "-----BEGIN PUBLIC KEY-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END PUBLIC KEY-----";
            }
            else
            {
                /****生成私钥****/
                var param = rsa.ExportParameters(true);

                //写入总字节数，后续写入
                ms.WriteByte(0x30);
                int index1 = (int)ms.Length;

                //写入版本号
                ms.WriteAll(_Ver);

                //PKCS8 多一段数据
                int index2 = -1, index3 = -1;
                if (usePKCS8)
                {
                    //固定内容
                    ms.WriteAll(_SeqOID);

                    //后续内容长度
                    ms.WriteByte(0x04);
                    index2 = (int)ms.Length;

                    //后续内容长度
                    ms.WriteByte(0x30);
                    index3 = (int)ms.Length;

                    //写入版本号
                    ms.WriteAll(_Ver);
                }

                //写入数据
                writeBlock(param.Modulus);
                writeBlock(param.Exponent);
                writeBlock(param.D);
                writeBlock(param.P);
                writeBlock(param.Q);
                writeBlock(param.DP);
                writeBlock(param.DQ);
                writeBlock(param.InverseQ);


                //计算空缺的长度
                var byts = ms.ToArray();

                if (index2 != -1)
                {
                    byts = writeLen(index3, byts);
                    byts = writeLen(index2, byts);
                }
                byts = writeLen(index1, byts);


                var flag = " PRIVATE KEY";
                if (!usePKCS8)
                {
                    flag = " RSA" + flag;
                }
                return "-----BEGIN" + flag + "-----\n" + RSA_Unit.TextBreak(RSA_Unit.Base64EncodeBytes(byts), 64) + "\n-----END" + flag + "-----";
            }
        }
    }

    /// <summary>
    /// 封装的一些通用方法
    /// </summary>
    class RSA_Unit
    {
        static public string Base64EncodeBytes(byte[] byts)
        {
            return Convert.ToBase64String(byts);
        }
        static public byte[] Base64DecodeBytes(string str)
        {
            try
            {
                return Convert.FromBase64String(str);
            }
            catch
            {
                return null;
            }
        }
        /// <summary>
        /// 把字符串按每行多少个字断行
        /// </summary>
        static public string TextBreak(string text, int line)
        {
            var idx = 0;
            var len = text.Length;
            var str = new StringBuilder();
            while (idx < len)
            {
                if (idx > 0)
                {
                    str.Append('\n');
                }
                if (idx + line >= len)
                {
                    str.Append(text.Substring(idx));
                }
                else
                {
                    str.Append(text.Substring(idx, line));
                }
                idx += line;
            }
            return str.ToString();
        }
    }

    static public class Extensions
    {
        /// <summary>
        /// 从数组start开始到指定长度复制一份
        /// </summary>
        static public T[] Sub<T>(this T[] arr, int start, int count)
        {
            T[] val = new T[count];
            for (var i = 0; i < count; i++)
            {
                val[i] = arr[start + i];
            }
            return val;
        }
        static public void WriteAll(this Stream stream, byte[] byts)
        {
            stream.Write(byts, 0, byts.Length);
        }
    }
}